package dcedit.util;

// Import Java SDK Classes
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.Collator;
import java.util.ArrayList;
import java.util.TreeMap;
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.TreeModel;

// Import Substance Classes
import org.pushingpixels.substance.api.SubstanceLookAndFeel;

// Import Application Classes
import dcedit.DCEdit;
import dcedit.factories.*;
import dcedit.cha.*;

/** Util class */
public class Util {
   /** Private Constructor */
   private Util() {} // constructor

   /** addNewTab method */
   public static void addNewTab() {
      addNewTab(Constants.NEW_TEXT);
   } // addNewTab

   /** addNewTab method */
   public static void addNewTab(File file) {
      try {
         addNewTab(file.getName());
         getSelectedTextPane().load(file, null);
      } catch(Exception e) {
         System.out.println("Error in Util::addNewTab.");
         System.out.println(e.getMessage());
      } // END try/catch
   } // addNewTab

   /** addNewTab method */
   public static void addNewTab(String title) {
      final DCTextEditor TEXT_PANE = new DCTextEditor(Constants.DEFAULT_SYNTAX_STYLE);
      final Dimension SIZE = new Dimension(800, 600);

      Constants.TAB_PANE.setSize(SIZE);
      Constants.TAB_PANE.setPreferredSize(SIZE);

      TEXT_PANE.setFG(Colors.getTabForegroundColor());
      TEXT_PANE.setBG(Colors.getTabBackgroundColor());
      TEXT_PANE.setHL(Colors.getTabHighlightColor());

      createCurrentFont(TEXT_PANE);
      TEXT_PANE.setSyntaxStyle((DCTextEditor.SyntaxStyle)Constants.LANGUAGE_LIST.getSelectedItem());

      TEXT_PANE.setAutoIndentEnabled(Constants.AUTO_INDENT_BTN.isSelected());
      TEXT_PANE.addMouseWheelListener(ActionFactory.createTabPaneMouseWheelListener());

      Constants.TAB_PANE.addTab(title, TEXT_PANE);
      Constants.TAB_PANE.setSelectedIndex(Constants.TAB_PANE.getTabCount() - 1);
   } // addNewTab

   /** createCurrentFont method */
   public static Font createCurrentFont() {
      final String SELECTED_FONT_NAME = (String)Constants.FONT_NAME_LIST.getSelectedItem();
      final Integer SELECTED_FONT_SIZE = (Integer)Constants.FONT_SIZE_LIST.getSelectedItem();
      final boolean IS_BOLD = Constants.BOLD_BTN.isSelected();
      final boolean IS_ITALIC = Constants.ITALIC_BTN.isSelected();

      int SELECTED_FONT_STYLE = Font.PLAIN;
      if(IS_BOLD && IS_ITALIC) SELECTED_FONT_STYLE = Font.BOLD + Font.ITALIC;
      else if(IS_BOLD) SELECTED_FONT_STYLE = Font.BOLD;
      else if(IS_ITALIC) SELECTED_FONT_STYLE = Font.ITALIC;

      return new Font(SELECTED_FONT_NAME, SELECTED_FONT_STYLE, SELECTED_FONT_SIZE);
   } // END createCurrentFont

   /** createCurrentFont method */
   public static void createCurrentFont(DCTextEditor textPane) {
      textPane.setFont(createCurrentFont());
   } // END createCurrentFont

   /** getSelectedIndex method */
   public static int getSelectedIndex() {
      return Constants.TAB_PANE.getSelectedIndex();
   } // END getSelectedIndex

   /** getSelectedTextPane method */
   public static DCTextEditor getSelectedTextPane() {
      return (DCTextEditor)Constants.TAB_PANE.getSelectedComponent();
   } // END getSelectedTextPane

   /** getTextPane method */
   public static DCTextEditor getTextPane(int index) {
      return (DCTextEditor)Constants.TAB_PANE.getComponentAt(index);
   } // END getTextPane

   /** getSelectedTab method */
   public static Component getSelectedTab() {
      return Constants.TAB_PANE.getTabComponentAt(Constants.TAB_PANE.getSelectedIndex());
   } // END getSelectedTab

   /** getTab method */
   public static Component getTab(int index) {
      return Constants.TAB_PANE.getTabComponentAt(index);
   } // END getTab

   /** getSelectedTabText method */
   public static String getSelectedTabText() {
      return getTabText(Constants.TAB_PANE.getSelectedIndex());
   } // END setSelectedTabText

   /** getTabText method */
   public static String getTabText(int index) {
      return Constants.TAB_PANE.getTitleAt(index);
   } // END setTabText

   /** setSelectedTabText method */
   public static void setSelectedTabText(String text) {
      setTabText(Constants.TAB_PANE.getSelectedIndex(), text);
   } // END setSelectedTabText

   /** setTabText method */
   public static void setTabText(int index, String text) {
      Constants.TAB_PANE.setTitleAt(index, text);
   } // END setTabText

   /** isTextPaneNew method */
   public static boolean isTextPaneNew(int index) {
      return getTabText(index).equals(Constants.NEW_TEXT) ||
             getTabText(index).equals(Constants.NEW_STAR_TEXT);
   } // END isTextPaneNew

   /** isSelectedTextPaneNew method */
   public static boolean isSelectedTextPaneNew() {
      return getSelectedTabText().equals(Constants.NEW_TEXT) ||
             getSelectedTabText().equals(Constants.NEW_STAR_TEXT);
   } // END isSelectedTextPaneNew

   /** getNewLineMultiplier method */
   public static int getNewLineMultiplier() {
      return (System.getenv().get(Constants.OPERATING_SYSTEM_PROPERTY).toLowerCase().contains(Constants.WINDOWS_PROPERTY) ? 1 : 2);
   } // getNewLineMultiplier

   /** countOccurances method */
   public static int countOccurrences(String mainStr, String subStr) {
      if(mainStr == null || subStr == null) return 0;

      int count = 0;
      int index = 0;

      while((index = mainStr.indexOf(subStr, index)) != -1) {
         ++index;
         ++count;
      } // END loop

      return count;
   } // END countOccurrences

   /** countNewLines method */
   public static int countNewLines(String str) {
      return countOccurrences(str, System.getProperty(Constants.LINE_SEPARATOR_PROPERTY));
   } // END countNewLines

   /** showTranslateErrorMessage method */
   public static void showTranslateErrorMessage(final JDialog DIALOG) {
      final StringBuilder MESSAGE = new StringBuilder();
      MESSAGE.append("The current text pane contains does not contain template tokens of the form ");
      MESSAGE.append(Constants.BEGIN_TEMPLATE_EXPRESSION_FIELD.getText());
      MESSAGE.append(Constants.ELLIPSIS);
      MESSAGE.append(Constants.END_TEMPLATE_EXPRESSION_FIELD.getText());
      JOptionPane.showMessageDialog(DIALOG, MESSAGE.toString());
   } // END showTranslateErrorMessage

   /** getTranslateTextTokens method */
   public static void getTranslateTextTokens() {
      getTranslateTextTokens(getSelectedTextPane());
   } // END getTranslateTextTokens

   /** getTranslateTextTokens method */
   public static void getTranslateTextTokens(final DCTextEditor TEXT_PANE) {
      getTranslateTextTokens(TEXT_PANE.getText());
   } // END getTranslateTextTokens

   /** getTranslateTextTokens method */
   public static void getTranslateTextTokens(final String TEXT) {
      final String BEGIN_TEMPLATE_EXPRESSION = Constants.BEGIN_TEMPLATE_EXPRESSION_FIELD.getText();
      final String END_TEMPLATE_EXPRESSION = Constants.END_TEMPLATE_EXPRESSION_FIELD.getText();

      Constants.TEMPLATE_TOKENS.clear();

      try {
         // Only get translate text tokens if both template expression fields contain text.
         if(!BEGIN_TEMPLATE_EXPRESSION.isEmpty() && !END_TEMPLATE_EXPRESSION.isEmpty()) {
            // for each template beginning.
            for(int index = 0 ; index < TEXT.length() && TEXT.indexOf(BEGIN_TEMPLATE_EXPRESSION, index) > -1 ; ++index) {
               index = TEXT.indexOf(BEGIN_TEMPLATE_EXPRESSION, index);
               int endIndex = TEXT.indexOf(END_TEMPLATE_EXPRESSION, index + 1);
               if(endIndex > -1) {
                  final int BEGIN_TOKEN = index;
                  final int END_TOKEN = endIndex + END_TEMPLATE_EXPRESSION.length();
                  final String TOKEN = TEXT.substring(BEGIN_TOKEN, END_TOKEN);
                  Constants.TEMPLATE_TOKENS.add(new TemplateToken(TOKEN));
                  index = endIndex;
               } // END if
            } // END loop
         } // END if
      } catch(Exception e) {
         System.out.println("Error in Util::getTranslateTextTokens.");
         System.out.println(e.getMessage());
      } // END try/catch
   } // END getTranslateTextTokens

   /** ensureDCEditConfigLocation method */
   public static File ensureDCEditConfigLocation(final String EXTERNAL_FILE) {
       TreeMap<String,String> envMap = new TreeMap<String,String>(Collator.getInstance());

       // Loop through all system property keys
       for(String key : System.getProperties().stringPropertyNames()) {
          String value = System.getProperties().getProperty(key);
          envMap.put(key,value);
       } // for each system property key.

       // Loop through all system environment keys
       for(String key : System.getenv().keySet()) {
          String value = System.getenv().get(key);
          envMap.put(key,value);
       } // for each system environment key.

      final String USER_HOME_PATH = envMap.get(Constants.USER_HOME_PATH_PROPERTY);
      final String DCEDIT_CONFIG_PATH = USER_HOME_PATH + File.separator + Constants.DCEDIT_CONFIG_DIR;
      final String EXTERNAL_CONFIG_PATH = DCEDIT_CONFIG_PATH + (null != EXTERNAL_FILE ? File.separator + EXTERNAL_FILE : "");

      final File DCEDIT_CONFIG_DIRECTORY = new File(DCEDIT_CONFIG_PATH);
      final File EXTERNAL_CONFIG_FILE = new File(EXTERNAL_CONFIG_PATH);

      try {
         if(!DCEDIT_CONFIG_DIRECTORY.exists()) DCEDIT_CONFIG_DIRECTORY.mkdir();
      } catch(Exception e) {
         System.out.println("Error in Util::ensureDCEditConfigLocation.");
         System.out.println(e.getMessage());
      } // END try/catch

      return EXTERNAL_CONFIG_FILE;
   } // END ensureDCEditConfigLocation

   /** saveOrSaveAs method */
   public static int saveOrSaveAs(int index) {
      try {
         // Check if this tab has been saved before.
         if(isTextPaneNew(index)) {
            // This is a new tab.  It has not been saved before.
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.FILES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showSaveDialog(getTextPane(index));

            // Only proceed if user confirmed the save.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               final File SELECTED_FILE = Constants.FILE_CHOOSER.getSelectedFile();
               getTextPane(index).saveAs(SELECTED_FILE);
               setTabText(index, SELECTED_FILE.getName());
            } // END if
         } else {
            // This is not a new tab, just save it.
            getTextPane(index).save();
            setTabText(index, getTabText(index).replace(Constants.ASTERISK_CHAR,""));
         } // END if/else
      } catch(Exception e) {
         System.out.println(e.getMessage());
      } // END try/catch

      // Return YES option by default
      return JOptionPane.YES_OPTION;
   } // END saveOrSaveAs

   /** closeAllTabs method */
   public static int closeAllTabs() {
      while(Constants.TAB_PANE.getTabCount() > 0) {
         try {
            // Get the first text pane in the list of text panes.
            final DCTextEditor TEXT_PANE = getTextPane(0);

            // Check if there are pending edits on the selected pane.
            if(!TEXT_PANE.isEmpty() && TEXT_PANE.isDirty()) {
               // Since we are going to prompt the user, show them the pane which we are prompting about.
               Constants.TAB_PANE.setSelectedIndex(0);

               // Prompt the user to save.
               final int OPTION = JOptionPane.showConfirmDialog(TEXT_PANE, Constants.DIRTY_SAVE_MESSAGE_TEXT);

               // Don't proceed if the user selected cancel.
               if(OPTION == JOptionPane.CANCEL_OPTION) {
                  return JOptionPane.CANCEL_OPTION;
               // Only save if user selected yes.
               } else if(OPTION == JOptionPane.YES_OPTION) {
                  saveOrSaveAs(0);
               } // END if/else
            } // END if

            // Remove the pane.
            Constants.TAB_PANE.remove(0);
         } catch(Exception e) {
            System.out.println("Error in Util::closeAllTabs.");
            System.out.println(e.getMessage());
         } // END try/catch
      } // END loop

      // Return YES option by default
      return JOptionPane.YES_OPTION;
   } // END closeAllTabs

   /** setDCEditDefaultColors method */
   public static void setDCEditDefaultColors() {
      System.out.println("Setting default color configuration.");
      try {
         // Default the look and feel to Raven style
         final String LF_CLASS = Constants.LF_PREFIX + Constants.LF_DEFAULT_NAME + Constants.LF_SUFFIX;

         // Default the skin to Raven style
         final String SKIN_CLASS = Constants.SKIN_PREFIX + Constants.LF_DEFAULT_NAME + Constants.SKIN_SUFFIX;

         // Activate the new look and feel for the entire app.
         UIManager.setLookAndFeel(LF_CLASS);
         SubstanceLookAndFeel.setSkin(SKIN_CLASS);
      } catch (Exception e) {
         System.out.println("Substance Look and Feel failed to initialize.");
      } // END try/catch

      Colors.setAppFontColor(Color.WHITE);
      Colors.setAppBackgroundColor(Color.DARK_GRAY);
      Colors.setMenuFontColor(Color.WHITE);
      Colors.setMenuBackgroundColor(Color.DARK_GRAY);
      Colors.setTabForegroundColor(Color.WHITE);
      Colors.setTabBackgroundColor(Color.BLACK);
      Colors.setTabHighlightColor(Color.DARK_GRAY);
      Colors.setHtmlForegroundColor(new Color(25, 50, 100));
      Colors.setJavaForegroundColor(new Color(25, 50, 100));
      Colors.setSqlForegroundColor(new Color(25, 50, 100));
      Colors.setSingleCommentLineForegroundColor(new Color(25, 100, 50));
      Colors.setMultiCommentLineForegroundColor(new Color(25, 100, 75));
      Colors.setJavaDocCommentLineForegroundColor(new Color(25, 100, 100));
   } // END setDCEditDefaultColors

   /** readDCEditColorConfig method */
   public static void readDCEditColorConfig() {
      final File COLOR_CONFIG_FILE = ensureDCEditConfigLocation(Constants.COLOR_CONFIG_SER);

      if(COLOR_CONFIG_FILE.exists()) {
         try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(COLOR_CONFIG_FILE));
            UIManager.setLookAndFeel(oin.readUTF());
            SubstanceLookAndFeel.setSkin(oin.readUTF());
            Colors.setAppFontColor((Color)oin.readObject());
            Colors.setAppBackgroundColor((Color)oin.readObject());
            Colors.setMenuFontColor((Color)oin.readObject());
            Colors.setMenuBackgroundColor((Color)oin.readObject());
            Colors.setTabForegroundColor((Color)oin.readObject());
            Colors.setTabBackgroundColor((Color)oin.readObject());
            Colors.setTabHighlightColor((Color)oin.readObject());
            oin.close();
         } catch(Exception e) {
            System.out.println("Error in Util::readDCEditColorConfig.");
            setDCEditDefaultColors();
         } // END try/catch
      } else {
         setDCEditDefaultColors();
         System.out.println(COLOR_CONFIG_FILE.getAbsolutePath() + " does not exist.");
      } // END if/else
   } // END readDCEditColorConfig

   /** haveDCEditCustomTemplates method */
   public static boolean haveDCEditCustomTemplates() {
      final File CUSTOM_TEMPLATES_DIR = ensureDCEditConfigLocation(Constants.TEMPLATES_PATH);

      try {
         if(CUSTOM_TEMPLATES_DIR.exists()) {
            if(CUSTOM_TEMPLATES_DIR.isDirectory()) {
               return (CUSTOM_TEMPLATES_DIR.listFiles().length > 0); // true of there are files in this directory.
            } else {
               return false; // not a directory of templates.
            } // END if/else
         } else {
            CUSTOM_TEMPLATES_DIR.mkdir(); // create the directory for future templates.
            return false; // no templates yet.
         } // END if/else
      } catch(Exception e) {
         System.out.println("Error in Util::haveDCEditCustomTemplates.");
         return false;
      } // END try/catch
   } // END haveDCEditCustomTemplates

   /** getDCEditCustomTemplates method */
   public static File[] getDCEditCustomTemplates() {
      final File CUSTOM_TEMPLATES_DIR = ensureDCEditConfigLocation(Constants.TEMPLATES_PATH);
      return (haveDCEditCustomTemplates() ? CUSTOM_TEMPLATES_DIR.listFiles() : new File[0]);
   } // END getDCEditCustomTemplates

   /** writeDCEditColorConfig method */
   public static void writeDCEditColorConfig() {
      final File COLORS_CONFIG_FILE = ensureDCEditConfigLocation(Constants.COLOR_CONFIG_SER);

      try {
         ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(COLORS_CONFIG_FILE));
         oout.writeUTF(UIManager.getLookAndFeel().getClass().getName());
         oout.writeUTF(SubstanceLookAndFeel.getCurrentSkin().getClass().getName());
         oout.writeObject(Colors.getAppFontColor());
         oout.writeObject(Colors.getAppBackgroundColor());
         oout.writeObject(Colors.getMenuFontColor());
         oout.writeObject(Colors.getMenuBackgroundColor());
         oout.writeObject(Colors.getTabForegroundColor());
         oout.writeObject(Colors.getTabBackgroundColor());
         oout.writeObject(Colors.getTabHighlightColor());
         oout.flush();
         oout.close();
      } catch(Exception e) {
         System.out.println("Error in Util::writeDCEditColorConfig.");
         System.out.println(e.getMessage());
      } // END try/catch
   } // END writeDCEditColorConfig

   /** readDCEditFontConfig method */
   public static void readDCEditFontConfig() {
      final File FONT_CONFIG_FILE = ensureDCEditConfigLocation(Constants.FONT_CONFIG_SER);

      if(FONT_CONFIG_FILE.exists()) {
         try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(FONT_CONFIG_FILE));
            Constants.LANGUAGE_LIST.setSelectedIndex(oin.readInt());
            Constants.FONT_NAME_LIST.setSelectedIndex(oin.readInt());
            Constants.FONT_SIZE_LIST.setSelectedIndex(oin.readInt());
            Constants.BOLD_BTN.setSelected(oin.readBoolean());
            Constants.ITALIC_BTN.setSelected(oin.readBoolean());
            Constants.AUTO_INDENT_BTN.setSelected(oin.readBoolean());
            oin.close();
         } catch(Exception e) {
            System.out.println("Error in Util::readDCEditFontConfig.");
            System.out.println(e.getMessage());
         } // END try/catch
      } else {
         System.out.println(FONT_CONFIG_FILE.getAbsolutePath() + " does not exist.");
      } // END if/else
   } // END readDCEditFontConfig

   /** writeDCEditFontConfig method */
   public static void writeDCEditFontConfig() {
      final File FONT_CONFIG_FILE = ensureDCEditConfigLocation(Constants.FONT_CONFIG_SER);

      try {
         ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(FONT_CONFIG_FILE));
         oout.writeInt(Constants.LANGUAGE_LIST.getSelectedIndex());
         oout.writeInt(Constants.FONT_NAME_LIST.getSelectedIndex());
         oout.writeInt(Constants.FONT_SIZE_LIST.getSelectedIndex());
         oout.writeBoolean(Constants.BOLD_BTN.isSelected());
         oout.writeBoolean(Constants.ITALIC_BTN.isSelected());
         oout.writeBoolean(Constants.AUTO_INDENT_BTN.isSelected());
         oout.flush();
         oout.close();
      } catch(Exception e) {
         System.out.println("Error in Util::writeDCEditFontConfig.");
         System.out.println(e.getMessage());
      } // END try/catch
   } // END writeDCEditFontConfig

   /** getListOfDCCharacters method */
   public static ArrayList<String> getListOfDCCharacters() {
      final ArrayList<String> DC_CHARACTER_FILES = new ArrayList<String>();

      final File DCEDIT_CONFIG_PATH = ensureDCEditConfigLocation(null);
      for(final String FILE_NAME : DCEDIT_CONFIG_PATH.list()) {
         if(FILE_NAME.startsWith(Constants.CHARACTER_PREFIX)) {
            DC_CHARACTER_FILES.add(FILE_NAME);
         } // END if
      } // END file loop

      return DC_CHARACTER_FILES;
   } // END getListOfDCCharacters

   /** readDCCharacter method */
   public static void readDCCharacter(final String DC_CHARACTER_NAME) {
      final File CHARACTER_SER_FILE = ensureDCEditConfigLocation(Constants.CHARACTER_PREFIX + DC_CHARACTER_NAME + Constants.SER_SUFFIX);

      if(CHARACTER_SER_FILE.exists()) {
         try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(CHARACTER_SER_FILE));
            final DCCharacter DC_CHARACTER = (DCCharacter)oin.readObject();
            oin.close();

            Constants.SPRITE_SPINNER.setSelectedImage(DC_CHARACTER.getAvatar());
            Constants.CHARACTER_GENDER_LIST.setSelectedItem(DC_CHARACTER.getGender());
            Constants.CHARACTER_RACES_LIST.setSelectedItem(DC_CHARACTER.getRace());
            Constants.CHARACTER_CLASSES_LIST.setSelectedItem(DC_CHARACTER.getClazz());
            Constants.ZODIAC_LIST.setSelectedItem(DC_CHARACTER.getZodiac());
            Constants.CHARACTER_NAME_FIELD.setText(DC_CHARACTER.getName());

            for(final DCTextField CHA_STAT_FIELD : Constants.CHARACTER_STAT_LIST) {
               final String LABEL = CHA_STAT_FIELD.getLabel().getText();

               if(LABEL.equals("Phys.Str")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getPhysicalStrength()));
               if(LABEL.equals("Phys.Def")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getPhysicalDefense()));
               if(LABEL.equals("Mag.Str")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getMagicalStrength()));
               if(LABEL.equals("Mag.Def")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getMagicalDefense()));
               if(LABEL.equals("Agility")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getAgility()));
               if(LABEL.equals("Constitution")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getConstitution()));
               if(LABEL.equals("Charisma")) CHA_STAT_FIELD.setText(String.valueOf(DC_CHARACTER.getCharisma()));

               CHA_STAT_FIELD.setHorizontalAlignment(JTextField.CENTER);
            } // END stat loop
         } catch(Exception e) {
            System.out.println("Error in Util::readDCCharacter.");
            System.out.println(e.getMessage());
         } // END try/catch
      } else {
         JOptionPane.showMessageDialog(null, "Character " + DC_CHARACTER_NAME + " could not be found", "Character Not Found", JOptionPane.WARNING_MESSAGE);
      } // END if/else
   } // END readDCCharacter

   /** writeDCCharacter method */
   public static void writeDCCharacter(final DCCharacter DC_CHARACTER) {
      final File CHARACTER_SER_FILE = ensureDCEditConfigLocation(Constants.CHARACTER_PREFIX + DC_CHARACTER.getName() + Constants.SER_SUFFIX);

      if(CHARACTER_SER_FILE.exists()) {
         // Character file exists.  Prompt user for overwrite.
         StringBuilder overwriteNotes = new StringBuilder(1024);
         overwriteNotes.append("A character with this name already exists.");
         overwriteNotes.append(Constants.NEW_LINE);
         overwriteNotes.append("Do you want to overwrite the character with this new one?");

         final int OVERWRITE_SELECTION = JOptionPane.showConfirmDialog(null, overwriteNotes.toString(), "Character Exists", JOptionPane.WARNING_MESSAGE);

         if(OVERWRITE_SELECTION == JOptionPane.YES_OPTION) {
            try {
               ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(CHARACTER_SER_FILE));
               oout.writeObject(DC_CHARACTER);
               oout.flush();
               oout.close();
               JOptionPane.showMessageDialog(null, "Character " + DC_CHARACTER.getName() + " was saved", "Character Saved", JOptionPane.INFORMATION_MESSAGE);
            } catch(Exception e) {
               System.out.println("Error in Util::writeDCCharacter.");
               System.out.println(e.getMessage());
            } // END try/catch
         } // END if
      } else {
         try {
            ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(CHARACTER_SER_FILE));
            oout.writeObject(DC_CHARACTER);
            oout.flush();
            oout.close();
            JOptionPane.showMessageDialog(null, "Character " + DC_CHARACTER.getName() + " was saved", "Character Saved", JOptionPane.INFORMATION_MESSAGE);
         } catch(Exception e) {
            System.out.println("Error in Util::writeDCCharacter.");
            System.out.println(e.getMessage());
         } // END try/catch
      } // END if/else
   } // END writeDCCharacter

   /**
    * getRandomInt method.
    * Generates a number between 1 and max (inclusive)
    * @param max The maximum int (inclusive)
    * @return A number between 1 and max (inclusive)
    */
   public static int getRandomInt(int max) throws IllegalArgumentException {
      if(max <= 0) throw new IllegalArgumentException("Error in Util::getRandomInt(max[" + max + "])::max must be positive.");
      else return Constants.RANDOM_GENERATOR.nextInt(max) + 1;
   } // END getRandomInt

   /**
    * getRandomInt method.
    * Generates a number between min and max (both inclusive)
    * @param min The minimum int (inclusive)
    * @param max The maximum int (inclusive)
    * @return A number between min and max (both inclusive)
    */
   public static int getRandomInt(int min, int max) throws IllegalArgumentException {
      if(min >= max) throw new IllegalArgumentException("Error in Util::getRandomInt(min[" + min + "], max[" + max + "])::max must be greater than min.");
      else return min + (Constants.RANDOM_GENERATOR.nextInt() % (max + 1 - min));
   } // END getRandomInt

   /** updateTemplateTokenTableModel method */
   public static void updateTemplateTokenTableModel() {
      updateTemplateTokenTableModel(getSelectedTextPane());
   } // END updateTemplateTokenTableModel

   /** updateTemplateTokenTableModel method */
   public static void updateTemplateTokenTableModel(final DCTextEditor TEXT_PANE) {
      updateTemplateTokenTableModel(TEXT_PANE.getText());
   } // END updateTemplateTokenTableModel

   /** updateTemplateTokenTableModel method */
   public static void updateTemplateTokenTableModel(final String TEMPLATE_TEXT) {
      // Clear the current table model.
      Constants.TEMPLATE_TOKEN_TABLE_MODEL.clear();

      // Get the default translate tokens.
      getTranslateTextTokens(TEMPLATE_TEXT);

      // For each token in set.
      for(final TemplateToken TEMPLATE_TOKEN : Constants.TEMPLATE_TOKENS) {
         Constants.TEMPLATE_TOKEN_TABLE_MODEL.addToken(TEMPLATE_TOKEN);
      } // END loop
   } // END updateTemplateTokenTableModel

   /** getCSSFromTreeModel method */
   public static String getCSSFromTreeModel(final TreeModel TREE_MODEL, final Object NODE) {
      // Create a string to contain all CSS definitions.
      String CSS = "";

      if(null != TREE_MODEL && null != NODE) {
         // Loop through all tree nodes.
         for(int ix = 0 ; ix < TREE_MODEL.getChildCount(NODE) ; ++ix) {
            // Get the next child node.
            final Object CHILD_NODE = TREE_MODEL.getChild(NODE, ix);

            // Is this node a leaf node?
            if(TREE_MODEL.isLeaf(CHILD_NODE)) {
               // Does this node contain CSS text?
               if(CHILD_NODE instanceof JavaDocStyleRuleNode) {
                  JavaDocStyleRuleNode CSS_NODE = (JavaDocStyleRuleNode)CHILD_NODE;
                  CSS += CSS_NODE.getStyleNameAndDefinition();
               } // END if
            } else {
               CSS += getCSSFromTreeModel(TREE_MODEL, CHILD_NODE);
            } // END if/else
         } // END loop
      } // END if/else

      // Nothing more to do, so return the CSS string.
      return CSS;
   } // END getCSSFromTreeModel

   /** setComponentSizes method */
   public static void setComponentSizes(final JComponent COMP, final Dimension DIM, final boolean MIN, final boolean MAX) {
      if(null != COMP && null != DIM) {
         COMP.setSize(DIM);
         COMP.setPreferredSize(DIM);
         if(MIN) COMP.setMinimumSize(DIM);
         if(MAX) COMP.setMaximumSize(DIM);
      } // END if
   } // END setComponentSizes

   /** setComponentSizes method */
   public static void setComponentSizes(final JComponent COMP, final int WIDTH, final int HEIGHT, final boolean MIN, final boolean MAX) {
      setComponentSizes(COMP,new Dimension(WIDTH,HEIGHT),MIN,MAX);
   } // END setComponentSizes

   /** prependTextLater method */
   public static void prependTextLater(final DCTextEditor PANE, final String TEXT) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            PANE.prependText(TEXT);
            PANE.scrollRectToVisible(PANE.getViewport().getViewRect());
         } // END run
      });
   } // END prependTextLater

   /** appendTextLater method */
   public static void appendTextLater(final DCTextEditor PANE, final String TEXT) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            PANE.appendText(TEXT);
            PANE.scrollRectToVisible(PANE.getViewport().getViewRect());
         } // END run
      });
   } // END appendTextLater

   /** prependTextNow method */
   public static void prependTextNow(final DCTextEditor PANE, final String TEXT) {
      try {
         SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
               PANE.prependText(TEXT);
               PANE.scrollRectToVisible(PANE.getViewport().getViewRect());
            } // END run
         });
      } catch(Exception e) {}
   } // END prependTextNow

   /** appendTextNow method */
   public static void appendTextNow(final DCTextEditor PANE, final String TEXT) {
      try {
         SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
               PANE.appendText(TEXT);
               PANE.scrollRectToVisible(PANE.getViewport().getViewRect());
            } // END run
         });
      } catch(Exception e) {}
   } // END appendTextNow

   /** prependTextLater method */
   public static void prependTextLater(final String TEXT) {
      prependTextLater(getSelectedTextPane(), TEXT);
   } // END prependTextLater

   /** appendTextLater method */
   public static void appendTextLater(final String TEXT) {
      appendTextLater(getSelectedTextPane(), TEXT);
   } // END appendTextLater

   /** prependTextNow method */
   public static void prependTextNow(final String TEXT) {
      prependTextNow(getSelectedTextPane(), TEXT);
   } // END prependTextNow

   /** appendTextNow method */
   public static void appendTextNow(final String TEXT) {
      appendTextNow(getSelectedTextPane(), TEXT);
   } // END appendTextNow

   /** updateDiceModeControls method */
   public static void updateDiceModeControls() {
      // Get the selected dice mod index from the dice mod option list.
      final int MOD_INDEX = Constants.DICE_MOD_OPTIONS_LIST.getSelectedIndex();

      // Check which dice mod option was selected.
      if(MOD_INDEX == 0) { // None
         Constants.DICE_MOD_SPINNER.setEnabled(false);
         Constants.DICE_MOD_SPINNER.setNumber(0);
      } else if(MOD_INDEX == 1) { // Add
         Constants.DICE_MOD_SPINNER.setEnabled(true);
      } else if(MOD_INDEX == 2) { // Subtract
         Constants.DICE_MOD_SPINNER.setEnabled(true);
      } else if(MOD_INDEX == 3) { // Multiply
         Constants.DICE_MOD_SPINNER.setEnabled(true);
      } else if(MOD_INDEX == 4) { // Divide
         Constants.DICE_MOD_SPINNER.setEnabled(true);
         if(Constants.DICE_MOD_SPINNER.getNumber() == 0) Constants.DICE_MOD_SPINNER.setNumber(1);
      } else if(MOD_INDEX == 5) { // Highest
         Constants.DICE_MOD_SPINNER.setEnabled(true);
         final int MOD = Constants.DICE_MOD_SPINNER.getNumber();
         final int QTY = Constants.DICE_QTY_SPINNER.getNumber();
         if(MOD > QTY) Constants.DICE_MOD_SPINNER.setNumber(QTY);
      } else if(MOD_INDEX == 6) { // Lowest
         Constants.DICE_MOD_SPINNER.setEnabled(true);
         final int MOD = Constants.DICE_MOD_SPINNER.getNumber();
         final int QTY = Constants.DICE_QTY_SPINNER.getNumber();
         if(MOD > QTY) Constants.DICE_MOD_SPINNER.setNumber(QTY);
      } // END if/else
   } // END updateDiceModeControls

   /** getTextFromFile method */
   public static String getTextFromFile(final String PATH, final boolean internalFile) {
      final StringBuffer buffer = new StringBuffer(1024);

      if(internalFile) {
         try(BufferedReader reader = new BufferedReader(new InputStreamReader(DCEdit.class.getResourceAsStream(PATH)))) {
            String inputLine = "";

            // Read the resource line-by-line and add all content to the string buffer. 
            while((inputLine = reader.readLine()) != null) {
               buffer.append(inputLine);
               buffer.append(Constants.NEW_LINE);
            } // END loop
         } catch(Exception e) {
            System.out.println(e.getMessage());
         } // END try/catch
      } else {
         try(BufferedReader reader = new BufferedReader(new FileReader(PATH))) {
            String inputLine = "";

            // Read the resource line-by-line and add all content to the string buffer. 
            while((inputLine = reader.readLine()) != null) {
               buffer.append(inputLine);
               buffer.append(Constants.NEW_LINE);
            } // END loop
         } catch(Exception e) {
            System.out.println(e.getMessage());
         } // END try/catch
      } // END if/else

      return buffer.toString();
   } // END getTextFromFile
} // END class Util
